在JVM中,能够存储操作数的空间主要包括局部变量和操作数栈,此外,还有一些常量数据允许是立即数,或者位于运行时常量池中。

局部变量、常量池和操作数栈之间的数据传送

  • Load类指令(数据方向:局部变量 –> 操作数栈)

    包括iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>

  • Store类指令(数据方向:操作数栈 –> 局部变量)

    包括istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>

  • 此外,还有一些指令能够将来自立即数或常量池的数据传送至操作数栈,这类指令包括bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>

数据传送指令

iload_<n>指令分析

iload_<n>指令的实现如下:

1
2
3
4
5
6
7
8
9
void TemplateTable::iload(int n)
{
transition(vtos, itos);
__ ldr(r0, iaddress(n));
}
static inline Address iaddress(int n) {
return Address(rlocals, Interpreter::local_offset_in_bytes(n));
}

上面的代码中,根据iaddress(n)取得第n个局部变量,iaddress函数封装了获取指定索引位置局部变量的功能:

1
2
3
static int local_offset_in_bytes(int n) {
return ((frame::interpreter_frame_expression_stack_direction() * n) * stackElementSize);
}

我们知道,此时局部变量地址存放在rlocals寄存器中,在本例中,iload_<0>指令的作用是将第一个int类型局部变量推送到栈顶,在执行这段Codelet时,从rlocals寄存器中取得局部变量首地址,即第一个局部变量地址,局部变量地址取得后,访问改地址的内存,就得到了第1个局部变量值。最后读取该值到r0寄存器返回。

对于iload_<1>、iload_<2>、iload_<3>,InterpreterCodelet根据rlocals的基址加偏移量来寻址局部变量。单需要注意的是:在计算偏移量时需要考虑栈的增长方向:栈的运动是按照内存地址变大还是变小。这与处理器架构有关,在x86和sparc架构上,栈都是向地址减小方向进行增长的,所以在iload_<1>计算第二个局部变量位置时,时用rlocals减去4来计算(0xfffffffc(%edi),即%edi + 0xfffffffc,其效果等同于%edi - 0x4)。

iload_0指令操作相比,iload_<1>获取第2个局部变量的操作区别仅在于所取得的局部变量地址不同,具体实现为:

1
mov 0xfffffffc(%edi), %eax

同理,iload_2获取第三个局部变量的操作实现为:

1
mov 0xfffffff8(%edi), %eax

iload_3获取第三个局部变量的操作实现为:

1
mov 0xfffffff4(%edi), %eax

另外,由于Hotspot使用了栈顶缓存技术,所以iload_<n>指令从局部变量取得数据后,该数据将被写入eax寄存器中。

iconst_<n>指令分析

常量-1、0、1、2、3、4、5的取值利用了处理器的立即数寻址模式,这样只需一条机器指令就可以实现取值:

1
2
3
4
5
6
7
iconst_ml: mov $0xffffffff, %eax
iconst_0: xor %eax, %eax
iconst _1: mov $0x1, %eax
iconst _2: mov $0x2, %eax
iconst _3: mov $0x3, %eax
iconst _4: mov $0x4, %eax
iconst _5: mov $0x5, %eax

istore_<n>指令分析

istore_<n>指令的数据传送方向与iload_<n>正好相反,但局部变量地址仍然存放在rdi(或edi)寄存器中。以istore_0指令为例,它将栈顶int类型数据存入第一个局部变量中。在执行该指令时,JVM首先将int类型数据从栈顶弹出到rax(或eax)寄存器中,然后将该寄存器值执行内存写指令,写入地址由rdi(或edi)寄存器表示的内存中,即第一个局部变量:

1
2
pop %eax
mov %eax, (%edi)

说明

本文摘录自《Hotspot实战》